﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

//------------------------------------------------------------
// shaco Framework
// Copyright © 2017-2021 chang.liu All rights reserved.
// Feedback: 449612236@qq.com
//------------------------------------------------------------

namespace shaco.Instance.Effect
{
    /// <summary>
    /// mesh残影效果组件，支持SkinnedMeshRenderer和MeshFilter
    /// 其中MeshFilter需要开启Read/Write才会生效
    /// </summary>
    public class EffectMeshShadowComponent : MonoBehaviour
    {
        //残影图像数据
        private class GhostImange
        {
            public Mesh combinedMesh;
            public Mesh sourceMesh;
            public Material material;
            public float duration;
            public float time;
            public Color baseColor;
        }

        //播放状态
        private enum PlayState
        {
            Stop,
            Pause,
            Playing
        }

        [SerializeField]
        [Tooltip("是否只渐变alpha值，当为false时候rgb值也会发生渐变(变灰色)")]
        private bool _alphaOnly = true;

        //公用效果材质
        public Material effectMaterial { get { return _effectMaterial; } }
        [SerializeField]
        [Tooltip("公用效果材质，\n当使用合并材质功能时候全部使用公用材质\n当使用非合并材质时候，如果设定了个别render使用合并材质的化也会使用公用材质")]
        private Material _effectMaterial = null;

        public System.Collections.ObjectModel.ReadOnlyCollection<Renderer> useEffectMaterialRenders { get { return _useEffectMaterialRenders.AsReadOnly(); } }
        [SerializeField]
        [Tooltip("当不使用合并材质功能时候，设置个别render是否使用公用材质")]
        private List<Renderer> _useEffectMaterialRenders = new List<Renderer>();

        public float duration { get { return _duration; } }
        [SerializeField]
        [Tooltip("残影总持续时长，当为-1时候则永远持续")]
        private float _duration = 5;

        public float interval { get { return _interval; } }
        [SerializeField]
        [Tooltip("产生残影的间隔")]
        private float _interval = 0.2f;

        public float fadeoutTime { get { return _fadeoutTime; } }
        [SerializeField]
        [Tooltip("残影淡出时间")]
        private float _fadeoutTime = 1;

        private float _currentTime = 5;
        private List<GhostImange> _imageList = new List<GhostImange>();
        private PlayState _playState = PlayState.Stop;
        private bool _isDurationForever = false;
        private IEnumerator _coroutineAddImage = null;
        private CombineInstance[] _cahcedCombineIntance = new CombineInstance[1];
        private Stack<Mesh> _cachedCombinedMesh = new Stack<Mesh>();
        private Stack<Mesh> _cachedSourceMesh = new Stack<Mesh>();
        private Stack<Material> _cachedMaterials = new Stack<Material>();
        private Stack<GhostImange> _cachedGhostImanges = new Stack<GhostImange>();
        private WaitForSeconds _cahcedWaitForSeconds = null;

        //播放残影动画，如果正在播放中，则本次播放无效，需要先stop或pause后再重新播放
        [ContextMenu("Play")]
        public void Play()
        {
            if (_playState == PlayState.Playing)
                return;

            _playState = PlayState.Playing;
            this.enabled = true;
            _currentTime = _duration;
            _isDurationForever = _duration == -1;
            _cahcedWaitForSeconds = new WaitForSeconds(_interval);
            
            if (null == _coroutineAddImage)
            {
                _coroutineAddImage = AddImage();
                StartCoroutine(_coroutineAddImage);
            }
        }

        //停止残影动画
        [ContextMenu("Stop")]
        public void Stop()
        {
            if (_playState == PlayState.Stop)
                return;

            _playState = PlayState.Stop;
        }

        //暂停残影显示
        public void Pause()
        {
            if (_playState == PlayState.Pause)
                return;

            _playState = PlayState.Pause;
            this.enabled = false;
        }
        
        IEnumerator AddImage()
        {
            while (_isDurationForever || _currentTime > 0)
            {
                if (_playState == PlayState.Playing)
                {
                    CreateImage();
                }

                yield return _cahcedWaitForSeconds;
                _currentTime -= _interval;
            }

            //等待残影播放结束
            while (_imageList.Count > 0)
            {
                yield return 1;
            }

            _playState = PlayState.Stop;
            _coroutineAddImage = null;
        }

        //从缓存中获取更适合的材质
        Material GetAdaptMaterialFromCache(Renderer render)
        {
            Material retValue = null;
            var findIndex = _useEffectMaterialRenders.IndexOf(render);
            var material = findIndex >= 0 ? _effectMaterial : render.material;

            if (_cachedMaterials.Count > 0)
            {
                retValue = _cachedMaterials.Pop();
                retValue.CopyPropertiesFromMaterial(material);
            }
            else
                retValue = new Material(material);
            return retValue; ;
        }

        void CreateImage()
        {
            SkinnedMeshRenderer[] skinRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
            MeshFilter[] filters = null;

            int filtersCount = 0;
            filters = GetComponentsInChildren<MeshFilter>();
            filtersCount = filters.Length;

            if (skinRenderers.Length + filtersCount <= 0)
            {
                Debug.LogErrorFormat("EffectMeshShadowComponent CreateImage error: No {0} or {1} was found", typeof(SkinnedMeshRenderer).FullName, typeof(MeshFilter).FullName);
                return;
            }

            for (int i = 0; i < skinRenderers.Length; ++i)
            {
                var render = skinRenderers[i];
                if (null == render || !render.enabled)
                    continue;

                var sourceMesh = _cachedSourceMesh.Count > 0 ? _cachedSourceMesh.Pop() : new Mesh();
                render.BakeMesh(sourceMesh);

                _cahcedCombineIntance[0].mesh = sourceMesh;
                _cahcedCombineIntance[0].transform = render.gameObject.transform.localToWorldMatrix;
                _cahcedCombineIntance[0].subMeshIndex = 0;

                Mesh combinedMesh = _cachedCombinedMesh.Count > 0 ? _cachedCombinedMesh.Pop() : new Mesh();
                combinedMesh.CombineMeshes(_cahcedCombineIntance);

                var newMaterial = GetAdaptMaterialFromCache(render);
                var newGhostImage = _cachedGhostImanges.Count > 0 ? _cachedGhostImanges.Pop() : new GhostImange();
                newGhostImage.sourceMesh = sourceMesh;
                newGhostImage.combinedMesh = combinedMesh;
                newGhostImage.material = GetAdaptMaterialFromCache(render);
                newGhostImage.time = _fadeoutTime;
                newGhostImage.duration = _fadeoutTime;
                newGhostImage.baseColor = render.material.color;

                _imageList.Add(newGhostImage);
            }

            for (int i = 0; i < filtersCount; ++i)
            {
                var meshFilter = filters[i];
                var render = meshFilter.GetComponent<Renderer>();

                if (!meshFilter.mesh.isReadable)
                    continue;

                if (null == render || !render.enabled)
                    continue;

                var temp = (null != meshFilter.sharedMesh) ? meshFilter.sharedMesh : meshFilter.mesh;
                var sourceMesh = _cachedSourceMesh.Count > 0 ? _cachedSourceMesh.Pop() : (Mesh)Object.Instantiate(temp);

                _cahcedCombineIntance[0].mesh = sourceMesh;
                _cahcedCombineIntance[0].transform = meshFilter.gameObject.transform.localToWorldMatrix;
                _cahcedCombineIntance[0].subMeshIndex = 0;

                Mesh combinedMesh = _cachedCombinedMesh.Count > 0 ? _cachedCombinedMesh.Pop() : new Mesh();
                combinedMesh.CombineMeshes(_cahcedCombineIntance);

                var newGhostImage = _cachedGhostImanges.Count > 0 ? _cachedGhostImanges.Pop() : new GhostImange();
                newGhostImage.sourceMesh = sourceMesh;
                newGhostImage.combinedMesh = combinedMesh;
                newGhostImage.material = GetAdaptMaterialFromCache(render);
                newGhostImage.time = _fadeoutTime;
                newGhostImage.duration = _fadeoutTime;
                newGhostImage.baseColor = render.material.color;

                _imageList.Add(newGhostImage);
            }
        }

        // void CreateCombineImage()
        // {
        //     if (null == _effectMaterial)
        //     {
        //         Debug.LogError("EffectMeshShadowComponent CreateCombineImage error: effect material can't be null");
        //         return;
        //     }

        //     SkinnedMeshRenderer[] skinRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
        //     MeshFilter[] filters = null;

        //     int filtersCount = 0;
        //     filters = GetComponentsInChildren<MeshFilter>();
        //     filtersCount = filters.Length;

        //     if (skinRenderers.Length + filtersCount <= 0)
        //     {
        //         Debug.LogErrorFormat("EffectMeshShadowComponent CreateCombineImage error: No {0} or {1} was found", typeof(SkinnedMeshRenderer).FullName, typeof(MeshFilter).FullName);
        //         return;
        //     }

        //     CombineInstance[] combineInstances = new CombineInstance[skinRenderers.Length + filtersCount];

        //     int idx = 0;
        //     for (int i = 0; i < skinRenderers.Length; ++i)
        //     {
        //         var render = skinRenderers[i];
        //         if (null == render || !render.enabled)
        //             continue;

        //         var mesh = new Mesh();
        //         render.BakeMesh(mesh);

        //         combineInstances[idx] = new CombineInstance
        //         {
        //             mesh = mesh,
        //             transform = render.gameObject.transform.localToWorldMatrix,
        //             subMeshIndex = 0
        //         };

        //         idx++;
        //     }

        //     for (int i = 0; i < filtersCount; ++i)
        //     {
        //         var meshFilter = filters[i];
        //         var render = meshFilter.GetComponent<Renderer>();

        //         if (!meshFilter.mesh.isReadable)
        //             continue;

        //         if (null == render || !render.enabled)
        //             continue;

        //         var temp = (null != meshFilter.sharedMesh) ? meshFilter.sharedMesh : meshFilter.mesh;
        //         var mesh = (Mesh)Object.Instantiate(temp);
        //         combineInstances[idx] = new CombineInstance
        //         {
        //             mesh = mesh,
        //             transform = meshFilter.gameObject.transform.localToWorldMatrix,
        //             subMeshIndex = 0
        //         };

        //         idx++;
        //     }

        //     Mesh combinedMesh = new Mesh();
        //     combinedMesh.CombineMeshes(combineInstances, true, true);

        //     _imageList.Add(new GhostImange
        //     {
        //         mesh = combinedMesh,
        //         material = new Material(_effectMaterial),
        //         time = _fadeoutTime,
        //         duration = _fadeoutTime,
        //         baseColor = _effectMaterial.color
        //     });
        // }

        void LateUpdate()
        {
            for (int i = _imageList.Count - 1; i >= 0; --i)
            {
                var imageTmp = _imageList[i];
                imageTmp.time -= Time.deltaTime;
                Color sourceColor = imageTmp.baseColor;
                var animationPercent = imageTmp.time / imageTmp.duration;
                animationPercent = Mathf.Clamp01(animationPercent);

                if (_alphaOnly)
                {
                    sourceColor.a = Mathf.Max(0, animationPercent * sourceColor.a);
                }
                else
                {
                    var averageColor = (sourceColor.r + sourceColor.g + sourceColor.b) / 3;
                    sourceColor.r = Mathf.Lerp(sourceColor.r, averageColor, (1 - animationPercent));
                    sourceColor.g = Mathf.Lerp(sourceColor.g, averageColor, (1 - animationPercent));
                    sourceColor.b = Mathf.Lerp(sourceColor.b, averageColor, (1 - animationPercent));
                    sourceColor.a = Mathf.Max(0, animationPercent * sourceColor.a);
                }
                imageTmp.material.color = sourceColor;

                Graphics.DrawMesh(imageTmp.combinedMesh, Matrix4x4.identity, imageTmp.material, gameObject.layer);

                //播放结束
                if (imageTmp.time <= 0)
                {
                    _cachedSourceMesh.Push(imageTmp.sourceMesh);
                    _cachedCombinedMesh.Push(imageTmp.combinedMesh);
                    _cachedMaterials.Push(imageTmp.material);
                    _cachedGhostImanges.Push(imageTmp);
                    _imageList.RemoveAt(i);
                }
            }
        }
    }

#if UNITY_EDITOR
    [UnityEditor.CustomEditor(typeof(EffectMeshShadowComponent))]
    public class EffectMeshShadowComponentInspector : UnityEditor.Editor
    {
        private EffectMeshShadowComponent _target = null;

        void OnEnable()
        {
            _target = target as EffectMeshShadowComponent;
        }

        override public void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            //当使用合并mesh模式时候必须有公用效果材质
            if (_target.useEffectMaterialRenders.Count > 0)
            {
                if (null == _target.effectMaterial)
                {
                    UnityEditor.EditorGUILayout.HelpBox("Effect Material can't be null when 'useComineMesh' or 'useEffectMaterialRenders' ", UnityEditor.MessageType.Error);
                }
            }
        }
    }
#endif
}